// Generics are type parameters
{
  function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
      // result[i] = value;
      result.push(value);
    }

    return result;
  }

  let strArray = createArray(3, "x");
  let intArray = createArray(10, 3);
}

{
  function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
  }

  let tuple = swap([7, "seven"]);
}

// generic constraints
{
  interface Lengthwise {
    length: number;
  }

  function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
  }
}

{
  function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
      target[id] = (<T>source)[id]; // type assertion
    }
    return target;
  }

  let x = { a: 1, b: 2, c: 3, d: 4 };

  copyFields(x, { b: 10, d: 20 });
  console.log(x);
}

// generic interfaces
{
  interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
  }

  let createArray: CreateArrayFunc<any>;
  createArray = function <T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
      result[i] = value;
    }
    return result;
  };

  createArray(3, "x"); // ['x', 'x', 'x']
}

// generic classes
{
  class GenericNumber<T> {
    zeroValue: T;

    constructor(zeroValue: T) {
      this.zeroValue = zeroValue;
    }

    add!: (x: T, y: T) => T;
  }

  let myGenericNumber = new GenericNumber<number>(0);
  myGenericNumber.add = function (x, y) {
    return x + y;
  };
}

// Default for type parameters
{
  function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
      result[i] = value;
    }
    return result;
  }
}
